package com.hzc.springchan.config.aspect;

import com.auth0.jwt.exceptions.TokenExpiredException;
import com.hzc.springchan.annotation.CApi;
import com.hzc.springchan.annotation.CParam;
import com.hzc.springchan.annotation.CParams;
import com.hzc.springchan.bean.exception.CException;
import com.hzc.springchan.config.resthelp.RequestConcurrent;
import com.hzc.springchan.constant.System;
import com.hzc.springchan.enums.CMethod;
import com.hzc.springchan.enums.PrivError;
import com.hzc.springchan.enums.PubError;
import com.hzc.springchan.service.ITokenService;
import com.hzc.springchan.strategy.paramcheck.ParamCheckContext;
import com.hzc.springchan.strategy.requestlog.RequestLogContext;
import com.hzc.springchan.strategy.restrequest.RestRequestContext;
import com.hzc.springchan.util.FlakeUtils;
import com.hzc.springchan.util.LogUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * 全局接口拦截日志配置类
 * @author chen990627
 * @date 2022/10/20
 */
@Aspect
@Component
public class WebLogAspect implements Ordered {

    @Autowired
    private RequestLogContext requestLogContext;

    @Autowired
    private RestRequestContext restRequestContext;

    @Autowired
    private ParamCheckContext paramCheckContext;

    @Autowired
    private RequestConcurrent requestConcurrent;

    @Autowired
    private ITokenService tokenService;

    @Pointcut("execution(public * com.hzc.springchan.controller.*.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void webLogDoBefore(JoinPoint point) throws NoSuchMethodException {

        MDC.put(System.MDC.TRANSACTION_ID, FlakeUtils.nextWorkerId());

        Class clazz = point.getTarget().getClass();
        String methodName = point.getSignature().getName();
        Class[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
        Method method = clazz.getMethod(methodName, parameterTypes);
        Annotation[] annotations = method.getDeclaredAnnotations();

        CMethod cMethod = CMethod.POST;
        CApi cApi = null;
        boolean anony = false;
        CParams cParams = null;
        CParam cParam = null;

        for (Annotation annotation : annotations) {
            if (annotation instanceof RequestMapping) {
                RequestMethod requestMethod = ((RequestMapping) annotation).method()[0];
                cMethod = RequestMethod.GET.equals(requestMethod) ? CMethod.GET : RequestMethod.POST.equals(requestMethod) ?
                CMethod.POST : RequestMethod.PUT.equals(requestMethod) ? CMethod.PUT : CMethod.DELETE;
            }
            if (annotation instanceof GetMapping) {
                cMethod = CMethod.GET;
            }
            if (annotation instanceof PostMapping) {
                cMethod = CMethod.POST;
            }
            if (annotation instanceof PutMapping) {
                cMethod = CMethod.PUT;
            }
            if (annotation instanceof DeleteMapping) {
                cMethod = CMethod.DELETE;
            }
            if (annotation instanceof CApi) {
                cApi = (CApi) annotation;
                anony = cApi.skipTokenCheck();
            }
            if (annotation instanceof CParams) {
                cParams = (CParams) annotation;
            }
            if (annotation instanceof CParam) {
                cParam = (CParam) annotation;
            }
        }

        if (cApi == null) {
            LogUtils.info(this, "接口未配置CApi");
            throw new CException(PrivError.CPERMISSION_UNDEFINED);
        }

        if (cApi.skipTokenCheck() && !cApi.skipTokenCheck()) {
            LogUtils.info(this, "CApi配置有误");
            throw new CException(PrivError.CPERMISSION_CONFIG_ERROR);
        }

        if (cApi.special()) {
            LogUtils.inter("POST: {}",cApi.specialRequestLog());
        }

        requestLogContext.requestLog(cMethod.getRequestLogStrategy(), point);

        restRequestContext.restRequest(point, cMethod);

        if (!anony) {
            //登录校验
            String token = (String) requestConcurrent.getCurrentRequest().get(System.RequestParam.TOKEN);
            if (StringUtils.isEmpty(token)) {
                LogUtils.info(this, "用户未登录");
                throw new CException(PubError.USER_NOT_LOGIN, PrivError.USER_NOT_LOGIN);
            }
            try {
                requestConcurrent.getCurrentRequest().setUserId(
                        tokenService.getValue(token, System.RequestParam.USER_ID, Integer.class, System.RequestParam.USER_ID)
                );
            } catch (TokenExpiredException e) {
                LogUtils.error("token已失效: {}", e);
                throw new CException(PrivError.TOKEN_EXPIRE);
            } catch (CException e) {
                LogUtils.error("token对应用户已退出登录");
                throw new CException(PrivError.TOKEN_EXPIRE_LOGOUT);
            } catch (Exception e) {
                LogUtils.error("token校验异常: {}", e);
                throw new CException(PrivError.INVALID_TOKEN);
            }
        }

        requestConcurrent.getCurrentRequest().setResourceName(cApi.resource().getName());

        if (cParams != null) {
            for (CParam temp : cParams.value()) {
                paramCheckContext.paramCheck(temp);
            }
        } else {
            if (cParam != null) {
                paramCheckContext.paramCheck(cParam);
            }
        }
    }

    @AfterReturning(returning = "response", pointcut = "webLog()")
    public void webLogDoAfterReturning(Object response) {

        requestConcurrent.removeCurrentRequest();
        LogUtils.inter("response: {}", response);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
